package com.googlecode.androidutils.database;

import static com.googlecode.androidutils.ArgumentUtils.notNull;
import static com.googlecode.androidutils.database.SQLiteDatabaseUtils.closeQuietly;

import java.util.ArrayList;
import java.util.List;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * This class provides more convenience methods to ease the handling of database
 * interactions.
 * 
 * @author Alexander Metzner
 */
public class SQLiteDatabaseTemplate {

	private final SQLiteOpenHelper sqLiteOpenHelper;

	/**
	 * Only assigns a reference to the given {@link SQLiteOpenHelper}
	 * 
	 * @param sqLiteOpenHelper
	 *            the {@link SQLiteOpenHelper} that is used in order to retrieve
	 *            a connection to work with. Must not be <code>null</code>.
	 */
	public SQLiteDatabaseTemplate(final SQLiteOpenHelper sqLiteOpenHelper) {
		this.sqLiteOpenHelper = notNull("sqLiteOpenHelper", sqLiteOpenHelper);
	}

	/**
	 * Executes the given query with a readable database and executes the given
	 * callback for every row returned by the cursor. Closes all resources and
	 * returns the results returned by every invocation of the callback.
	 * 
	 * @param <T>
	 *            the type of object to exepect
	 * @param queryBuilder
	 *            the query builder
	 * @param cursorReader
	 *            the cursor reader
	 * @return the list of read objects
	 */
	public <T> List<T> queryAllFromReadableDatabase(
			final QueryBuilder queryBuilder, final CursorReader<T> cursorReader) {
		return doWithReadableDatabase(new DatabaseCallback<List<T>>() {
			public List<T> doWithDatabase(final SQLiteDatabase database) {
				final List<T> result = new ArrayList<T>();
				final Cursor cursor = queryBuilder.query(database);
				try {
					while (cursor.moveToNext()) {
						result.add(cursorReader.readFromCursor(cursor));
					}
				} finally {
					closeQuietly(cursor);
				}
				return result;
			}
		});
	}

	/**
	 * Executes the given query with a readable database and executes the given
	 * callback for every the first row returned by the cursor. Closes all
	 * resources and returns the result of the callback.
	 * 
	 * Clones the query builder prior to executing it and applies a limit to one
	 * row.
	 * 
	 * @param <T>
	 *            the type of object to exepect
	 * @param queryBuilder
	 *            the query builder
	 * @param cursorReader
	 *            the cursor reader
	 * @return the list of read objects
	 */
	public <T> T queryFirstFromReadableDatabase(
			final QueryBuilder queryBuilder, final CursorReader<T> cursorReader) {
		final QueryBuilder limitedQueryBuilder = queryBuilder.clone();
		limitedQueryBuilder.limit(1);

		return doWithReadableDatabase(new DatabaseCallback<T>() {
			public T doWithDatabase(final SQLiteDatabase database) {
				final Cursor cursor = limitedQueryBuilder.query(database);
				try {
					if (cursor.moveToNext()) {
						return cursorReader.readFromCursor(cursor);
					} else {
						return null;
					}
				} finally {
					closeQuietly(cursor);
				}
			}
		});
	}

	/**
	 * Executes the given callback with a writable database within a database
	 * transaction. If the callback returns without an Exception the transaction
	 * is committed, otherwise its rolled back. Closes all resources afterwards
	 * and returns the result.
	 * 
	 * @param <T>
	 *            the type of object to exepect
	 * @param callback
	 *            the callback to execute
	 * @return the result returned by the callback
	 */
	public <T> T doWithWritableDatabaseInTransaction(
			DatabaseCallback<T> callback) {
		final SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();
		try {
			database.beginTransaction();
			final T result = callback.doWithDatabase(database);
			database.setTransactionSuccessful();
			return result;
		} finally {
			database.endTransaction();
			database.close();
		}
	}

	/**
	 * Executes the given callback with a readable database. Closes all
	 * resources afterwards and returns the result.
	 * 
	 * @param <T>
	 *            the type of object to exepect
	 * @param callback
	 *            the callback to execute
	 * @return the result returned by the callback
	 */
	public <T> T doWithReadableDatabase(DatabaseCallback<T> callback) {
		final SQLiteDatabase database = sqLiteOpenHelper.getReadableDatabase();
		try {
			return callback.doWithDatabase(database);
		} finally {
			closeQuietly(database);
		}
	}
}
